package com.owc.service.impl.app;

import com.owc.common.context.ActiveProfile;
import com.owc.common.enums.*;
import com.owc.common.exception.BadRequestException;
import com.owc.common.exception.LoginException;
import com.owc.common.exception.NoPermissionException;
import com.owc.common.redis.CacheBuildKey;
import com.owc.common.redis.RedisUtil;
import com.owc.common.tool.BeanUtils;
import com.owc.common.tool.CryptographUtils;
import com.owc.common.tool.UtilsService;
import com.owc.domain.dto.Authority;
import com.owc.domain.dto.PayPwd;
import com.owc.domain.dto.UserInfo;
import com.owc.domain.entity.User;
import com.owc.domain.vo.app.LoginVo;
import com.owc.domain.vo.app.ModifyVo;
import com.owc.domain.vo.app.RegisterVo;
import com.owc.filter.content.SecurityContextHolder;
import com.owc.filter.content.UserDetail;
import com.owc.service.OwcService;
import com.owc.service.admin.AdminService;
import com.owc.service.app.PermissionService;
import com.owc.service.app.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.Optional;

/**
 * @Author xiaom
 * @Date 2019/12/2 14:35
 * @Version 1.0.0
 * @Description <>
 **/
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    private final UserService userService;
    private final OwcService owcService;
    private final AdminService adminService;

    public PermissionServiceImpl(UserService userService,OwcService owcService,AdminService adminService) {
        this.userService = userService;
        this.owcService = owcService;
        this.adminService = adminService;
    }

    @Override
    public Authority register(RegisterVo registerVo) {
        //Check if the passwords are consistent
        if(!registerVo.getPassword().equals(registerVo.getConfirmPassword())){
            throw new BadRequestException("The password is not the same twice");
        }

        //Check if the account exists.
        if(userService.phoneIsRegister(registerVo.getPhone())){
            throw new BadRequestException("Mobile number has been registered");
        }

        if(!ActiveProfile.isDev()){
            //Check if the verification code is correct
            invalidCode(registerVo.getPhone(),registerVo.getAuthCode());
        }
        //Create user info.
        userService.createUser(registerVo.getPhone(),registerVo.getPassword());
        //Go to login.
        return login(new LoginVo(registerVo.getPhone(),registerVo.getPassword()));
    }

    @Override
    public Authority login(LoginVo loginVo) {

        Optional<LoginTypeEnum> opt = LoginTypeEnum.match(loginVo.getType());
        if(!opt.isPresent()) {
            throw new BadRequestException(ExceptionEnum.INVALID_TYPE_ERR);
        }

        switch (opt.get()){
            case AUTH_CODE:
                return new Authority(loginByAuthCode(loginVo));
            case PASSWORD_TYPE:
                return new Authority(loginByPassword(loginVo));
            default:
                throw new BadRequestException("Missing required parameter [type]");
        }
    }

    @Override
    public UserInfo getUserInfo() {
        Optional<User> optUser = userService.selectByUserName(SecurityContextHolder.getCurrentUserName());

        if(!optUser.isPresent()) {
            throw new NoPermissionException(ExceptionEnum.USER_INVALID_ERR);
        }
        return Objects.requireNonNull(BeanUtils.transformFrom(optUser.get(), UserInfo.class));
    }

    @Override
    public String loginByPassword(LoginVo loginVo){
        if(loginVo.getPassword() == null) {
            throw new BadRequestException(ExceptionEnum.PLEASE_ENTER_PASSWORD);
        }
        //Get user info by phone.
        Optional<User> optUser = userService.selectByPhone(loginVo.getPhone());
        if(!optUser.isPresent()) {
            throw new BadRequestException(ExceptionEnum.NOT_FOUND_USER);
        }

        //verify password
        User user = optUser.get();
        if(!user.getPassword().equals(CryptographUtils.MD5(loginVo.getPassword()))) {
            throw new BadRequestException(ExceptionEnum.USERNAME_OR_PWD_ERR);
        }

        //Issue of JWT certificate
        String token = PermissionService.signatureJwtToken(new UserDetail(owcService.converterFromExample(optUser.get()).orElse(null)));

        //Cache user token mapper.
        userService.cacheUserJwtToken(optUser.get().getId(),token);

        return token;
    }

    @Override
    public String loginByAuthCode(LoginVo loginVo){
        if(loginVo.getAuthCode() == null){
            throw new BadRequestException(ExceptionEnum.PLEASE_ENTER_AUTHCODE);
        }

        //Get user info by phone.
        Optional<User> optUser = userService.selectByPhone(loginVo.getPhone());

        if(!optUser.isPresent()){
            throw new BadRequestException(ExceptionEnum.NOT_FOUND_USER);
        }

        String codeCacheKey = SendCodeEnum.LOGIN.getRule().execute(loginVo.getPhone());

        //Verify auth code.
        String cacheCode = RedisUtil._REDIS_MODULE.get(codeCacheKey);
        if(cacheCode == null) {
            log.error("Request login interface failed. Because redis cache verification code is null.");
            throw new BadRequestException(ExceptionEnum.BAD_REQUEST_ERR);
        }

        if(!cacheCode.equals(loginVo.getAuthCode())) {
            throw new BadRequestException("Verification code error");
        }
        //Clear cache auth code.
        RedisUtil._REDIS_MODULE.del(codeCacheKey);

        //Issue of JWT certificate
        String token = PermissionService.signatureJwtToken(new UserDetail(owcService.converterFromExample(optUser.get()).orElse(null)));

        //Cache user token mapper.
        userService.cacheUserJwtToken(optUser.get().getId(),token);

        return token;
    }

    private void invalidCode(String phone, Integer authCode) {
        String cacheCode = RedisUtil._REDIS_MODULE.get(SendCodeEnum.REGISTER.getRule().execute(phone));
        if(cacheCode == null || !cacheCode.equals(authCode + "")){
            throw new BadRequestException("Verification code error");
        }
        //Clear auth code cache.
        RedisUtil._REDIS_MODULE.del(CacheBuildKey.buildRegisterAuthCodeCacheKey(phone));
    }

    @Override
    public Optional<UserDetail> getUserDetailByClient(String userName, Integer clientType) {

        Optional<ClientEnum> optClient = ClientEnum.match(clientType);

        if(!optClient.isPresent()){
            throw new BadRequestException(ExceptionEnum.INVALID_CLIENT_TYPE_ERR);
        }

        UserDetail userDetail;
        ExceptionEnum baseException = ExceptionEnum.NOT_FOUND_USER;

        switch (optClient.get()) {
            case IOS:
            case ANDROID:
                userDetail = owcService.converterFromExample(UtilsService.assertNotNull(userService.selectByUserName(userName),baseException))
                        .map(UserDetail::new)
                        .orElseThrow(() -> new BadRequestException(baseException));
                break;
            case WEB:
                userDetail = owcService.converterFromExample(UtilsService.assertNotNull(adminService.selectByUserName(userName),baseException))
                        .map(UserDetail::new)
                        .orElseThrow(() -> new BadRequestException(baseException));
                break;
            default:
                throw new BadRequestException(ExceptionEnum.NOT_FOUND_USER);
        }
        return Optional.ofNullable(userDetail);
    }

    @Override
    public Boolean modifyPassword(ModifyVo modifyVo) {

        Optional<ModifyEnum> optionalModifyEnum = ModifyEnum.match(modifyVo.getType());
        if(!optionalModifyEnum.isPresent()){
            throw new BadRequestException(ExceptionEnum.ENUM_FOUND_ERR);
        }

        String cacheKey = SendCodeEnum.BACKPASSWORD.getRule().execute(SecurityContextHolder.getCurrentUser().getUser().getPhone());
        if(StringUtils.isBlank(cacheKey)){
            throw new BadRequestException(ExceptionEnum.UNKNOWN_ERR);
        }

        //Get current request mapper's verification code.
        String cacheVal = RedisUtil._REDIS_MODULE.get(cacheKey);

        if(StringUtils.isBlank(cacheVal)){
            throw new BadRequestException("Please send mobile verification code first");
        }

        //Check verification code.
        if(!cacheVal.trim().equals(modifyVo.getAuthCode().toString())){
            throw new BadRequestException(ExceptionEnum.INVALID_AUTH_CODE);
        }

        //Clear already use verification code.
        RedisUtil._REDIS_MODULE.del(cacheKey);

        switch (optionalModifyEnum.get()){
            case PAY_PWD:
                userService.updatePayPassword(SecurityContextHolder.getCurrentUserId(),modifyVo.getPassword());
                break;
            case LOGIN_PWD:
                userService.updateLoginPassword(SecurityContextHolder.getCurrentUserId(),modifyVo.getPassword());
                break;
            default:
                log.error("Failed to change password. Modify password type:[{}]",optionalModifyEnum.get().getDesc());
        }
        return true;
    }

    @Override
    public PayPwd checkIsSetPayPwd() {
        String payPassword = userService.getPayPassword(SecurityContextHolder.getCurrentUserId());
        return new PayPwd(((payPassword == null || payPassword.isEmpty()) ? 0 : 1));
    }
}
